/**
  ETFDAQ Project
  \class TADAQ
  \brief The overall DAQ controlling class
  \author SUN Yazhou, asia.rabbit@163.com
  \since 2024-08-17
  \date 2024-08-17 last modified

  \copyright Copyright (c) 2024 IMP-CAS with LGPLv3 LICENSE
*/

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <libgen.h>
#include <ctime>
#include <signal.h>
#include <sys/stat.h>
#include "TADAQ.h"
#include "TASIS3100.h"
#include "TACrate.h"
#include "TACard.h"
#include "TAReadShm.h"
#include "TAYaml.h"
#include "TAMsg.h"
#include "TAMath.h"
#include "event.h"

using std::flush;

#define er TAMsg::Error
#define wn TAMsg::Warn

TADAQ::TADAQ(TAController *ctl) : fController(ctl), fCrate(0), fConfig(0),
fHasInitalized(0), fDebug(0), fEv(0), fBlock(new block_t), fEvIndex(0),
fBlkIndex(0), fdw(-1), fShm(0), fIsOnline(0), fT0(0), fDaqSta(EDAQSTA::UDF){
  if(!ctl) er("TADAQ", "ctor: nullptr controller");
} // end ctor

// dev is the device name of the vme controller
// FIXME currently we use sis3100 as controller; other types will later be included
TADAQ::TADAQ(const string &dev) : fController(new TASIS3100(dev)), fCrate(0),
fConfig(0), fHasInitalized(0), fDebug(0), fEv(0), fBlock(new block_t), fEvIndex(0),
fBlkIndex(0), fdw(-1), fShm(0), fIsOnline(0), fT0(0){}

TADAQ::~TADAQ(){
  if(fController) delete fController;
  if(fCrate) delete fCrate;
  if(fConfig) delete fConfig;
  if(fShm) delete fShm;
  if(fBlock) delete fBlock;
} // end dtor

void TADAQ::Initialize(){
  if(fHasInitalized){
    TAMsg::Warn("TADAQ", "Initialize: has called.");
    return;
  } // end if

  TAYaml c("../etfdaq/config/exp.yaml");
  if(!c["config"])
    er("TADAQ", "Initialize: \'config\' not defined in <srcdir>/config/exp.yaml");
  else fConfig = new TAYaml("../etfdaq/config/yaml/" + c["config"].as<string>());
  fCrate = new TACrate(fController, fConfig, 0);
  if((*fConfig)["debug"]) fDebug = (*fConfig)["debug"].as<bool>();
  if(fDebug) fCrate->SetDebug();
  
  if((*fConfig)["online"]) fIsOnline = (*fConfig)["online"].as<bool>();
  // create an shm (not just read one) for online monitoring
  if(fIsOnline) fShm = new TAReadShm("online_shm_VME", kVME, true);

  cout << std::boolalpha << "Debug: " << fDebug;
  cout << ", Online: " << fIsOnline << endl << std::noboolalpha;

  // configure the controller
  fController->SetConfig(fConfig);
  // open device, set signals, disable IRQ, and clear the outputs
  fController->Initialize();
  // configure the crate
  fCrate->Initialize(); // add and config cards following user input
  fCrate->Print();

  if(access("data", F_OK)) mkdir("data", S_IRWXU | S_IRWXG | S_IROTH);
  fDataDir = TAMsg::Form("%s/data", get_current_dir_name());

  fHasInitalized = true;
} // end member function Initialize

static void handler_ctrlc(int sig){ TAMsg::SetIRP(); }
// ctrl-C to end a run
void TADAQ::TerminalMode() const{
  struct sigaction sa;
  sa.sa_handler = handler_ctrlc;
  sa.sa_flags = 0;
  if(-1 == sigaction(SIGINT, &sa, 0)) perror("TADAQ::TerminalMode, sigaction");
} // end member function TerminalMode

void TADAQ::OpenFile(const string &comment){
  fEvIndex = fBlkIndex = 0;
  fT0 = time(NULL); // the DAQ start time in sec since EPOCH

  // create the data file //
  // 0640: user: read/write; group: read; others: no access whatsoever
  // TAMsg::time0: current time in YYYYMMDD_HHMM
  fFileName = TAMsg::Form("%s/%sv.dat", fDataDir.data(), TAMsg::time0(true));
  // deal with duplicate names //
  int cpy = 0;
  while(!access(fFileName.data(), F_OK)) // already exist
    fFileName = TAMsg::Form("%s/%sv%d.dat",
      fDataDir.data(), TAMsg::time0(true), cpy++);
  if((fdw = creat(fFileName.data(), 0640)) < 0)
    er("TADAQ", "OpenFile: create data file %s error: %s", fFileName.data(), strerror(errno));
  TAMsg::Info("TADAQ", "OpenFile: file %s created", fFileName.data());
  // the first block of the data file only stores the comment
  string header = TAMsg::Form("  ETFDAQ -- START TIME => %s."
    "\nUSER COMMENT: %s", TAMsg::sec0(), comment.data());
  strcpy((char *)fBlock, header.data());
  const ssize_t nw = write(fdw, fBlock, BLKSIZE);
  if(BLKSIZE != nw){
    if(-1 == nw) er("TADAQ", "OpenFile: write error: %s", strerror(errno));
    else wn("TADAQ", "OpenFile: written size (%d) != requested size(%d)", nw, BLKSIZE);
  } // end if
  fBlock->init(); // prepare for the physical data recording

  ClearShm();
  fCrate->ResetForNewRun(); // now only meant for v830 to clear its counters
} // end member function OpenFile

void TADAQ::Run(){
  while(1){ // loop over events
    // cout << "waiting for an IRQ..." << endl;
    SuspendForIRQ();
    DropDead(); // set ch_out to forbit incoming triggers
    ReadEvent(); // module buffer => controller buffer
    ClearData();
    Resurrect(); // clear the set ch_out to allow incoming triggers, and enable irq
    // cout << "Event index " << fEvIndex << endl;
    if(!(fEvIndex%1000)) cout << "Event index " << fEvIndex << '\r' << flush;
    if(TAMsg::irp()){
      usleep(1000);
      break;
    } // end if
  } // end while
} // end member function Run

void TADAQ::CloseFile(const string &comment){
  if(fdw < 0) return; // invalid

  // stores the (maybe half-full) last block first //
  if(fBlock->ev_num) DumpData(false); // false: not full, just've got to dump it

  // then add the ending block with user comment //
  // the last block of the data file only stores the comment
  fBlock->clear();
  string trailer = TAMsg::Form("  ETFDAQ -- STOP TIME => %s."
    "\nUSER COMMENT: %s", TAMsg::sec0(), comment.data());
  strcpy((char *)fBlock, trailer.data());
  const ssize_t nw = write(fdw, fBlock, BLKSIZE);
  if(BLKSIZE != nw){
    if(-1 == nw) er("TADAQ", "CloseFile: write error: %s", strerror(errno));
    else wn("TADAQ", "CloseFile: written size (%d) != requested size(%d)", nw, BLKSIZE);
  } // end if

  if(close(fdw) < 0)
    er("TADAQ", "dtor: close %s error: %s", fFileName.data(), strerror(errno));
  else fdw = -2; // mark the closed file
  TAMsg::Info("TADAQ", "CloseFile: data file %s closed, with %u events saved",
    fFileName.data(), fEvIndex);
} // end member function CloseFile

// module buffer => PC memory
void TADAQ::ReadEvent(){
  // 0. prepare for the next event
  fBlock->initEv(fEv); // prepare for next event
  fEv->index = fEvIndex++; // event index starts from 0
  // fBlock->print(); fEv->print(); // DEBUG
  // badEvent = crate->ReadEvent(&event), error e.g., ev_len exceeding the limit
  // bool badEvent = false; // reserved for multi-crate use
  // 1. collect data from the crate; read them via fEv directly into the block
  fCrate->ReadEvent(fEv);
  fBlock->last_pos += fEv->length;
  fBlock->ev_num++;
  // 2. if the block doesn't have enough room to accommodate this event...
  // ...dump the block to disk first
  if(fBlock->last_pos > BLKLEN) DumpData();
  // 3. write to the shmared memory for online use
  if(fIsOnline) fShm->write(fEv);
} // end member function ReadEvent

// clear the DAQ for next event
void TADAQ::ClearData(){
  // clear the multi-event buffers of the cards
  // reset their read and write pointers, and...
  fCrate->ClearData(); // ...clear the event counters
  // reset signal in TAMsg to -1 => so that IRQed() return false
  TAMsg::ClearSignal(); // the program depends it for IRQ detection in nonblock mode
} // end member function Clear

// clear cards' event counter for next run
void TADAQ::ClearEventCounter(){
  fCrate->ClearEventCounter();
} // end member function ClearEventCounter

// PC memory => disk & shm
// isFull: write because the block is full
void TADAQ::DumpData(bool isFull){
  // remove the last event
  if(isFull){
    fBlock->last_pos -= fEv->length;
    fBlock->ev_num--;
  } // end if

  // dump the block
  if(fdw >= 0){
    const ssize_t nw = write(fdw, fBlock, BLKSIZE);
    if(BLKSIZE != nw){
      if(-1 == nw) er("TADAQ", "DumpData: write error: %s", strerror(errno));
      else wn("TADAQ", "DumpData: written size (%d) != requested size(%d)", nw, BLKSIZE);
    } // end if(write error)
  } // end if
  fBlock->reset(); // prepare for next block
  fBlkIndex = fBlock->index;

  // move the last event to the head of the new block
  if(isFull){
    memcpy(fBlock->ev, fEv, fEv->length*4);
    fBlock->ev_num++;
    fBlock->last_pos += fEv->length;
  } // end if
} // end member function DumpData

// clean the shm to no data state
void TADAQ::ClearShm(){
  if(!fIsOnline) return;
  fShm->CleanShm(); // unlock the mutex using sem_post
  fShm->InitShm(); // clean by sem_init and memset to 0
} // end member function ClearShm

// if any interrupt request is pending
void TADAQ::SuspendForIRQ(){
  fController->SuspendForIRQ();
} // end member function SuspendForIRQ
// whether an IRQ has arrived
bool TADAQ::IsIRQed(){
  return fController->IsIRQed();
} // end member function IsIRQed
void TADAQ::EnableIRQ(){
  fController->EnableIRQ();
} // end member function EnableIRQ
// disable the trigger
void TADAQ::DropDead(){
  fController->DropDead();
} // end member function DropDead
// recover the trigger
void TADAQ::Resurrect(){
  fController->Resurrect();
} // end member function Resurrect

const char *TADAQ::GetFileName() const{
  static char name[128]; strcpy(name, fFileName.data());
  return basename(name);
} // end member function GetFileName

double TADAQ::GetFileSizeInMB() const{
  if(fdw < 0) return 0.;
  return TAMath::filesizeInMB(fFileName);
} // end member function GetFileSizeInMB

uint32_t TADAQ::GetEvLen() const{
  if(fEv) return fEv->length;
  return 0;
} // end member function GetEvLen
